import os
import sys
import traceback
import socket
import json
from datetime import datetime

from PyQt6.QtWidgets import (
    QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QMainWindow, QLineEdit, QComboBox, QSpinBox,
    QDoubleSpinBox, QStyle, QHBoxLayout, QFrame
)
from PyQt6.QtCore import QUrl, QTimer, QPropertyAnimation, QEasingCurve, Qt
from PyQt6.QtGui import QFont, QTextCursor, QPixmap
from PyQt6.QtNetwork import QNetworkRequest, QNetworkAccessManager, QNetworkReply
from qdarkstyle import load_stylesheet, LightPalette, DarkPalette

from server import FlaskServer, SERVER_IP, SERVER_PORT  # 导入后端模块
from UI.UI_MainWindow import Ui_MainWindow
from modbus_rtu_client import ModbusRTUClient
from log import LogManager

# modbus_rtu_client = ModbusRTUClient(port='COM13', slave_address=1)

SERIAL_PARAMS = {'port': 'COM13', 'baudrate': 115200, 'slave_address': 1}
# 动态获取资源路径
if getattr(sys, 'frozen', False):
    # 打包后的路径：资源在临时解压目录 (sys._MEIPASS)
    base_path = sys._MEIPASS
else:
    # 开发时的路径：基于当前文件的位置
    base_path = os.path.dirname(os.path.abspath(__file__))


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        # 记录初始尺寸和字体基准值
        self.base_width = 800  # 设计基准宽度
        self.base_height = 600  # 设计基准高度
        self.base_font_size = 10  # 基准字体大小(pt)
        self.min_font_size = 8  # 最小字体限制
        self.max_font_size = 24  # 最大字体限制
        self.server_ip = SERVER_IP
        self.log = LogManager()
        self.manager = QNetworkAccessManager()
        self.server = FlaskServer()  # 实例化后端
        self.setupUi(self)
        self.init_ui()
        # self.init_server()
        self.init_signals()

    def init_ui(self):
        self.setWindowTitle("429三合一单机工具")
        self.log.bind_widget(self.plainTextEdit)
        self.set_ports_select()
        self.set_frame_logo()
        # self.get_local_ip()

    def init_server(self):
        """启动后端服务并检测状态"""
        self.server.run()
        self.start_health_check()


    def init_signals(self):
        """连接信号槽"""
        self.pushButton.clicked.connect(self.fetch_time)
        self.pushButton_2.clicked.connect(self.fetch_device_params)
        self.pushButton_3.clicked.connect(self.fetch_device_data)
        self.actionLight.triggered.connect(self.handle_theme_toggle)
        self.actionDark.triggered.connect(self.handle_theme_toggle)
        self.comboBox.currentIndexChanged.connect(self.on_combo_box_changed)
        self.comboBox_2.currentIndexChanged.connect(self.on_baudrate_changed)
        self.comboBox_13.currentIndexChanged.connect(self.on_debug_changed)

    def closeEvent(self, event):
        """窗口关闭时清理资源"""
        if self.server.is_running:
            self.server.stop()
            event.accept()

    def resizeEvent(self, event):
        """ 重写窗口调整事件 """
        super().resizeEvent(event)
        self.adjust_fonts()

    def adjust_fonts(self):
        """ 动态调整所有控件字体 """
        # 计算当前窗口比例因子
        width_ratio = self.width() / self.base_width
        height_ratio = self.height() / self.base_height
        scale_factor = min(width_ratio, height_ratio)  # 取保守比例

        # 计算动态字体大小（二次曲线平滑过渡）
        new_size = int(self.base_font_size * (0.6 + 0.4 * scale_factor))
        new_size = max(min(new_size, self.max_font_size), self.min_font_size)

        # 创建新字体对象
        new_font = self.font()
        new_font.setPointSize(new_size)

        # 递归设置所有子控件字体
        def set_child_font(widget):
            widget.setFont(new_font)
            for child in widget.children():
                if isinstance(child, QWidget):
                    set_child_font(child)

        set_child_font(self)
        print(f"字体已调整为：{new_size}pt")  # 调试输出

    def set_frame_logo(self):
        self.frame_5.setFrameShape(QFrame.Shape.NoFrame)
        logo_label = QLabel()
        logo_label.setFixedSize(123, 60)  # 关键修改点：固定控件尺寸

        # 加载图片时添加错误处理
        logo_path = os.path.join(base_path, "UI/public_logo.png")
        if not os.path.exists(logo_path):
            raise FileNotFoundError(f"Logo文件缺失：{logo_path}")

        pixmap = QPixmap(logo_path)

        # 精确缩放（保持宽高比，自动裁剪）
        scaled_pix = pixmap.scaled(
            123, 60,
            Qt.AspectRatioMode.KeepAspectRatioByExpanding,  # 扩展模式保证填满
            Qt.TransformationMode.SmoothTransformation
        )

        # 创建裁剪区域
        cropped_pix = scaled_pix.copy(
            (scaled_pix.width() - 115) // 2,  # X起始点
            (scaled_pix.height() - 56) // 2,  # Y起始点
            115,
            56
        )

        logo_label.setPixmap(cropped_pix)
        self.horizontalLayout_7.addWidget(logo_label)

    def handle_theme_toggle(self):
        # 获取触发动作的对象
        sender = self.sender()
        print(f"Theme changed by: {sender.objectName()}")

        # 防止信号递归触发
        self.actionLight.blockSignals(True)
        self.actionDark.blockSignals(True)

        try:
            # 同步两个动作的状态
            if sender == self.actionLight:
                new_state = self.actionLight.isChecked()
                self.actionDark.setChecked(not new_state)
                print(f"Light theme {'activated' if new_state else 'deactivated'}")
            else:
                new_state = self.actionDark.isChecked()
                self.actionLight.setChecked(not new_state)
                print(f"Dark theme {'activated' if new_state else 'deactivated'}")

            # 执行主题切换逻辑
            self.handle_theme_change()
        finally:
            # 恢复信号连接
            self.actionLight.blockSignals(False)
            self.actionDark.blockSignals(False)

    def handle_theme_change(self):
        """预留的主题切换入口方法"""
        # 当前主题状态
        is_light = self.actionLight.isChecked()

        # 这里可以添加具体的主题切换逻辑
        print(f"切换至 {'亮' if is_light else '暗'} 主题")
        self.record_log(f"切换至 {'【亮】' if is_light else '【暗】'} 主题")
        # 示例样式切换（实际样式需要自行实现）
        if is_light:
            app.setStyleSheet(load_stylesheet(LightPalette))
        else:
            app.setStyleSheet(load_stylesheet(DarkPalette))

    def start_health_check(self):
        """定时检测服务可用性"""
        self.health_timer = QTimer()
        self.health_timer.timeout.connect(self.check_server_ready)
        self.health_timer.start(500)

    def check_server_ready(self):
        """尝试连接服务器"""
        if self.comboBox_13.currentIndex() == 2:
            self.server_ip = self.lineEdit_32.text()
        test_request = QNetworkRequest(QUrl(f"http://{self.server_ip}:{SERVER_PORT}/api/time"))
        reply = self.manager.get(test_request)
        reply.finished.connect(
            lambda: self.handle_health_check(reply)
        )

    def handle_health_check(self, reply):
        if reply.error() != QNetworkReply.NetworkError.NoError:
            print("服务器错误:", reply.error())
            self.health_timer.setInterval(5000)  # 每5秒检测
            self.record_log(f"服务器错误:{reply.error()}", "ERROR")
            self.pushButton.setEnabled(False)
            self.pushButton_2.setEnabled(False)
            self.pushButton_3.setEnabled(False)
            return  # 服务未就绪
        self.health_timer.stop()
        self.toolButton.setText("服务在线")
        self.record_log("服务在线", "INFO")
        self.pushButton.setEnabled(True)
        self.pushButton_2.setEnabled(True)
        self.pushButton_3.setEnabled(True)
        reply.deleteLater()

    def fetch_time(self):
        """发起业务请求"""
        request = QNetworkRequest(QUrl(f"http://{self.server_ip}:{SERVER_PORT}/api/time"))
        reply = self.manager.get(request)
        reply.finished.connect(lambda: self.handle_time_response(reply))

    def handle_time_response(self, reply):
        if reply.error() != QNetworkReply.NetworkError.NoError:
            print("请求失败")
        else:
            data = bytes(reply.readAll()).decode()
            print(f"服务器时间: {eval(data)['time']}")
        reply.deleteLater()

    def fetch_device_params(self):
        """获取设备参数"""
        self.tabWidget.setCurrentIndex(0)
        query_string = '&'.join(f"{k}={v}" for k, v in SERIAL_PARAMS.items())
        url = f"http://{self.server_ip}:{SERVER_PORT}/api/params?{query_string}"
        request = QNetworkRequest(QUrl(url))
        reply = self.manager.get(request)
        reply.finished.connect(lambda: self.handle_params_response(reply))

    def fetch_device_data(self):
        """获取设备参数"""
        self.tabWidget.setCurrentIndex(1)
        query_string = '&'.join(f"{k}={v}" for k, v in SERIAL_PARAMS.items())
        url = f"http://{self.server_ip}:{SERVER_PORT}/api/data?{query_string}"
        request = QNetworkRequest(QUrl(url))
        reply = self.manager.get(request)
        reply.finished.connect(lambda: self.handle_data_response(reply))

    def handle_params_response(self, reply):
        if reply.error() != QNetworkReply.NetworkError.NoError:
            print("请求失败")
        else:
            json_data = bytes(reply.readAll()).decode()
            data = json.loads(json_data)
            print(f"设备参数: {data}")
            if len(data) > 0 and 'error' not in data['data']:
                self.set_device_status_enhanced(is_online=True)
            else:
                error_message = data.get('error', '无数据')
                self.record_log(f"发生错误:{error_message}", "ERROR")
                self.set_device_status_enhanced(is_online=False)
            try:
                self.show_params(data['data'])
            except Exception as e:
                print(e)
                print(traceback.format_exc())
                self.record_log(f"Error:{e}\n{traceback.format_exc()}", 'ERROR')
            self.record_log(f"设备参数:{json.dumps(data, indent=4, ensure_ascii=False)}", "INFO")
        reply.deleteLater()

    def handle_data_response(self, reply):
        if reply.error() != QNetworkReply.NetworkError.NoError:
            print("请求失败")
        else:
            json_data = bytes(reply.readAll()).decode()
            data = json.loads(json_data)
            print(f"设备数据: {data}")
            if len(data) > 0 and 'error' not in data['data']:
                self.set_device_status_enhanced(is_online=True)
            else:
                error_message = data.get('error', '无数据')
                self.record_log(f"发生错误:{error_message}", "ERROR")
                self.set_device_status_enhanced(is_online=False)
            try:
                self.show_data(data['data'])
            except Exception as e:
                print(e)
                print(traceback.format_exc())
                self.record_log(f"Error:{e}\n{traceback.format_exc()}", 'ERROR')
            self.record_log(f"设备数据:{json.dumps(data, indent=4, ensure_ascii=False)}", "INFO")
        reply.deleteLater()

    def set_ports_select(self):
        """
        设置端口选择下拉框
        :return:
        """
        # 清空下拉框
        self.comboBox.clear()

        # 初始化 com_manager 的端口为 None
        # modbus_rtu_client.port = None

        # 遍历端口列表
        for port in ModbusRTUClient.detect_serial_ports():
            print(f'get ports: {port.description}')

            # 将所有端口描述添加到下拉框
            self.comboBox.addItem(port.description)
        current_items = [self.comboBox.itemText(i) for i in range(self.comboBox.count())]
        for index, item in enumerate(current_items):
            if 'USB Serial Port' in item:
                # 设置该选项为当前选项
                self.comboBox.setCurrentIndex(index)
                break
        # 更新其他参数
        self.comboBox_2.setCurrentIndex(2)
        # modbus_rtu_client.baudrate = self.comboBox_2.currentText()
        # modbus_rtu_client.slave_id = int(self.spinBox_2.text())
        SERIAL_PARAMS['baudrate'] = self.comboBox_2.currentText()
        SERIAL_PARAMS['slave_address'] = int(self.spinBox_2.text())

    def on_combo_box_changed(self, index):
        """
        下拉框选择端口
        :param index:
        :return:
        """
        try:
            com_item_text = self.comboBox.currentText()
            for port in ModbusRTUClient.detect_serial_ports():
                if port.description == com_item_text:
                    # modbus_rtu_client.port = port.device
                    SERIAL_PARAMS['port'] = port.device
                    break

            self.record_log(f"当前端口已切换为: {SERIAL_PARAMS['port']}")
            print(f"Selected option: {com_item_text}")
        except Exception as e:
            print(f"Error: {e}", traceback.format_exc())
            self.record_log(f"Error:{e}\n{traceback.format_exc()}", 'ERROR')

    def on_baudrate_changed(self, index):
        """
        下拉框选择端口
        :param index:
        :return:
        """
        try:
            com_item_text = self.comboBox_2.currentText()
            # modbus_rtu_client.baudrate = com_item_text
            SERIAL_PARAMS['baudrate'] = com_item_text
            self.record_log(f"波特率已切换为: {SERIAL_PARAMS['baudrate']}")
            print(f"Selected option: {com_item_text}")
        except Exception as e:
            print(f"Error: {e}", traceback.format_exc())
            self.record_log(f"Error:{e}\n{traceback.format_exc()}", 'ERROR')

    def on_debug_changed(self, index):
        """
        下拉框选择端口
        :param index:
        :return:
        """
        try:
            debug_mode = self.comboBox_13.currentText()
            # modbus_rtu_client.baudrate = com_item_text
            self.record_log(f"远程调试模式已: {debug_mode}")
            print(f"Selected option: {debug_mode}")
        except Exception as e:
            print(f"Error: {e}", traceback.format_exc())
            self.record_log(f"Error:{e}\n{traceback.format_exc()}", 'ERROR')
        if index == 1:
            self.lineEdit_32.setReadOnly(True)
            self.label_71.setText("   本地IP：")
            self.get_local_ip()
            self.init_server()
        elif index == 0:
            self.lineEdit_32.setReadOnly(True)
            self.lineEdit_32.setText("127.0.0.1")
            self.label_71.setText("   本地IP：")
            if self.server.is_running:
                self.server.stop()
        else:
            self.lineEdit_32.setReadOnly(False)
            self.lineEdit_32.setText("192.168.0.xx")
            self.label_71.setText("   对端IP：")

    def record_log(self, message, level='INFO'):
        try:
            self.log.log(level, message)
        except Exception as e:
            print(f"Error: {e}")
            traceback.print_exc()

    def get_local_ip(self, ):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(("8.8.8.8", 80))
            ip = s.getsockname()[0]
            s.close()
            self.record_log(f"本机IP地址: {ip}")
        except Exception as e:
            self.record_log(f"Error:{e}\n{traceback.format_exc()}", 'ERROR')
            ip = "127.0.0.1"
        self.lineEdit_32.setText(ip)

    def set_device_status(self, is_online: bool):
        """设置设备在线状态指示器
        Args:
            is_online: True=在线 False=离线
        """
        status = "在线" if is_online else "离线"
        color = "#4CAF50" if is_online else "#F44336"  # 绿色=在线 红色=离线

        self.toolButton.setStyleSheet(f"""
            QToolButton {{
                border: 2px solid {color};
                border-radius: 8px;
                padding: 2px;
                background-color: {color};
                color: white;
                font-weight: bold;
            }}
            QToolButton::menu-indicator {{ image: none; }}  /* 隐藏菜单箭头 */
        """)

        # 同时设置文本和工具提示
        self.toolButton.setText(f" {status} ")
        self.toolButton.setToolTip(f"设备状态: {status}")

        # # 可选：设置图标
        # icon = QStyle.StandardPixmap.SP_DialogApplyButton if is_online else \
        #     QStyle.StandardPixmap.SP_DialogCancelButton
        # self.toolButton.setIcon(self.style().standardIcon(icon))

    def set_device_status_enhanced(self, is_online: bool):
        """带动画效果的状态切换"""
        # 先设置基础样式
        self.set_device_status(is_online)

        # 添加缩放动画
        anim = QPropertyAnimation(self.toolButton, b"geometry")
        anim.setDuration(300)
        anim.setEasingCurve(QEasingCurve.Type.OutBack)

        orig_rect = self.toolButton.geometry()
        anim.setStartValue(orig_rect.adjusted(5, 5, -5, -5))
        anim.setEndValue(orig_rect)
        anim.start()

    # 文本类控件
    @staticmethod
    def set_text_control(control: QLineEdit | QLabel, value: str):
        """设置文本框/标签文本"""
        control.setText(str(value))
        control.setCursorPosition(0)  # 对于QLineEdit重置光标位置

    # 下拉框
    @staticmethod
    def set_combobox(control: QComboBox, value: int | str):
        """设置下拉框选中项"""
        if isinstance(value, int):
            control.setCurrentIndex(value)
        else:
            index = control.findText(str(value))
            if index >= 0:
                control.setCurrentIndex(index)
            elif control.isEditable():
                control.setCurrentText(str(value))

    # 数值输入框
    @staticmethod
    def set_numeric_control(control: QSpinBox | QDoubleSpinBox, value: int | float):
        """设置数值输入框"""
        if isinstance(control, QSpinBox):
            control.setValue(int(float(value)))  # 先转float再转int，确保正确处理字符串
        elif isinstance(control, QDoubleSpinBox):
            control.setValue(float(value))

    def show_params(self, params_data):
        self.set_numeric_control(self.spinBox_2, params_data.get('slave_id', 1))
        self.set_combobox(self.comboBox_3, params_data.get('baudrate', 115200))
        self.set_combobox(self.comboBox_7, params_data.get('pf_trigger_switch', '--'))
        self.set_text_control(self.lineEdit, params_data.get('phase_offset', '--'))
        self.set_combobox(self.comboBox_4, params_data.get('denoising_level', '--'))
        self.set_combobox(self.comboBox_5, params_data.get('show_noise', '--'))
        self.set_text_control(self.lineEdit_33, params_data.get('device_code', '--'))
        self.set_text_control(self.lineEdit_34, params_data.get('soft_version', '--'))
        self.set_combobox(self.comboBox_6, str(params_data.get('tev_auto_threshold_toggle', '--')))
        self.set_combobox(self.comboBox_10, str(params_data.get('uhf_auto_threshold_toggle', '--')))
        self.set_combobox(self.comboBox_12, str(params_data.get('aa_auto_threshold_toggle', '--')))
        self.set_numeric_control(self.spinBox_3, params_data.get('tev_abs_threshold', 0))
        self.set_numeric_control(self.spinBox_16, params_data.get('uhf_abs_threshold', 0))
        self.set_numeric_control(self.spinBox_24, params_data.get('aa_abs_threshold', 0))
        self.set_numeric_control(self.spinBox_4, params_data.get('tev_amplitude_alarm_threshold', 0))
        self.set_numeric_control(self.spinBox_17, params_data.get('uhf_amplitude_alarm_threshold', 0))
        self.set_numeric_control(self.spinBox_25, params_data.get('aa_amplitude_alarm_threshold', 0))
        self.set_numeric_control(self.spinBox_5, params_data.get('tev_frequency_alarm_threshold', 0))
        self.set_numeric_control(self.spinBox_18, params_data.get('uhf_frequency_alarm_threshold', 0))
        self.set_numeric_control(self.spinBox_26, params_data.get('aa_frequency_alarm_threshold', 0))
        self.set_numeric_control(self.spinBox_6, params_data.get('tev_m_value', 0))
        self.set_numeric_control(self.spinBox_12, params_data.get('uhf_m_value', 0))
        self.set_numeric_control(self.spinBox_20, params_data.get('aa_m_value', 0))
        self.set_numeric_control(self.spinBox_7, params_data.get('tev_base_frequency', 0))
        self.set_numeric_control(self.spinBox_15, params_data.get('uhf_base_frequency', 0))
        self.set_numeric_control(self.spinBox_23, params_data.get('aa_base_frequency', 0))
        self.set_numeric_control(self.spinBox_8, params_data.get('tev_db_value', 0))
        self.set_numeric_control(self.spinBox_13, params_data.get('uhf_db_value', 0))
        self.set_numeric_control(self.spinBox_21, params_data.get('aa_db_value', 0))
        self.set_numeric_control(self.spinBox_9, params_data.get('tev_difference_config', 0))
        self.set_numeric_control(self.spinBox_14, params_data.get('uhf_difference_config', 0))
        self.set_numeric_control(self.spinBox_22, params_data.get('aa_difference_config', 0))
        self.set_numeric_control(self.spinBox_10, params_data.get('tev_additional_base', 0))
        self.set_numeric_control(self.spinBox_11, params_data.get('uhf_additional_base', 0))
        self.set_numeric_control(self.spinBox_19, params_data.get('aa_additional_base', 0))
        self.set_combobox(self.comboBox_8, params_data.get('tev_waveform_filter_enabled', '--'))
        self.set_combobox(self.comboBox_9, params_data.get('uhf_waveform_filter_enabled', '--'))
        self.set_combobox(self.comboBox_11, params_data.get('aa_waveform_filter_enabled', '--'))

    def show_data(self, data):
        """
        }
        :param data:
        :return:
        """

        self.set_text_control(self.lineEdit_2, data.get('tev_amplitude', '--'))
        self.set_text_control(self.lineEdit_3, data.get('tev_frequency', '--'))
        self.set_text_control(self.lineEdit_4, data.get('tev_noise_level', '--'))
        self.set_text_control(self.lineEdit_5, data.get('tev_noise_mean', '--'))
        self.set_text_control(self.lineEdit_6, data.get('tev_pulse_cycles', '--'))
        self.set_text_control(self.lineEdit_7, data.get('tev_severity', '--'))
        self.set_text_control(self.lineEdit_8, data.get('tev_50hz', '--'))
        self.set_text_control(self.lineEdit_9, data.get('tev_100hz', '--'))
        self.set_text_control(self.lineEdit_10, data.get('tev_fft_frequency', '--'))
        self.set_text_control(self.lineEdit_11, data.get('tev_max_fft_frequency', '--'))
        self.set_text_control(self.lineEdit_12, data.get('uhf_amplitude', '--'))
        self.set_text_control(self.lineEdit_13, data.get('uhf_frequency', '--'))
        self.set_text_control(self.lineEdit_14, data.get('uhf_noise_level', '--'))
        self.set_text_control(self.lineEdit_15, data.get('uhf_avg_discharge_current', '--'))
        self.set_text_control(self.lineEdit_16, data.get('uhf_pulse_cycles', '--'))
        self.set_text_control(self.lineEdit_17, data.get('uhf_max_discharge_capacity', '--'))
        self.set_text_control(self.lineEdit_35, data.get('uhf_min_discharge_capacity', '--'))
        self.set_text_control(self.lineEdit_18, data.get('uhf_50hz', '--'))
        self.set_text_control(self.lineEdit_19, data.get('uhf_100hz', '--'))
        self.set_text_control(self.lineEdit_20, data.get('uhf_fft_frequency', '--'))
        self.set_text_control(self.lineEdit_21, data.get('uhf_max_fft_frequency', '--'))
        self.set_text_control(self.lineEdit_22, data.get('aa_amplitude', '--'))
        self.set_text_control(self.lineEdit_23, data.get('aa_frequency', '--'))
        self.set_text_control(self.lineEdit_24, data.get('aa_noise_level', '--'))
        self.set_text_control(self.lineEdit_25, data.get('aa_mean_value', '--'))
        self.set_text_control(self.lineEdit_27, data.get('aa_effective_value', '--'))
        self.set_text_control(self.lineEdit_36, data.get('aa_frequency_component1', '--'))
        self.set_text_control(self.lineEdit_37, data.get('aa_frequency_component2', '--'))
        self.set_text_control(self.lineEdit_38, data.get('aa_alarm_level', '--'))
        self.set_text_control(self.lineEdit_28, data.get('aa_50hz', '--'))
        self.set_text_control(self.lineEdit_29, data.get('aa_100hz', '--'))
        self.set_text_control(self.lineEdit_30, data.get('aa_fft_frequency', '--'))
        self.set_text_control(self.lineEdit_31, data.get('aa_max_fft_frequency', '--'))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyleSheet(load_stylesheet(LightPalette))
    client = MainWindow()
    client.show()
    sys.exit(app.exec())
